/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import {ArkFile, ts} from "arkanalyzer";
import Logger, {LOG_MODULE_TYPE} from 'arkanalyzer/lib/utils/logger';
import {BaseChecker, BaseMetaData} from "../BaseChecker";
import {Defects} from "../../Index";
import {FileMatcher, MatcherCallback, MatcherTypes} from "../../Index";
import {Rule} from "../../Index";
import {RuleListUtil} from "../../utils/common/DefectsList";
import {IssueReport} from "../../model/Defects";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoShadowCheck');

const gMetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/no-shadow-check.md",
    description: "Disallow variable declarations from shadowing variables declared in the outer scope."
};

type RuleOptions = {
    ignoreTypeValueShadow: boolean,
    ignoreFunctionTypeParameterNameValueShadow: boolean
}

type CheckMember = {
    name: string | undefined;
    node: ts.Node
};

export class NoShadowCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private ruleOptions: RuleOptions = { ignoreTypeValueShadow: true, ignoreFunctionTypeParameterNameValueShadow: true }

    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE
    };

    public registerMatchers(): MatcherCallback[] {
        const fileMatchBuildCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        }
        return [fileMatchBuildCb];
    }

    public check = (targetFile: ArkFile) => {
        let options = this.rule.option;
        if (options.length > 0) {
            this.ruleOptions = options[0] as RuleOptions;
        }

        const sourceFile = ts.createSourceFile('temp.ts', targetFile.getCode(), ts.ScriptTarget.Latest, true);
        const sourceFileObject = ts.getParseTreeNode(sourceFile);
        if (sourceFileObject == undefined) {
            return;
        }

        this.loopNode(targetFile, sourceFile, sourceFileObject);
    }

    public loopNode(targetFile: ArkFile, sourceFile: ts.SourceFileLike, aNode: ts.Node) {
        const children = aNode.getChildren();
        for (const child of children) {
            const members = this.objectNeedCheck(child, false);
            for (const checkMember of members) {
                this.checkShadow(targetFile, sourceFile, checkMember.node, checkMember.name);
            }

            this.loopNode(targetFile, sourceFile, child);
        }
    }

    private checkShadow(targetFile: ArkFile, sourceFile: ts.SourceFileLike, aNode: ts.Node, name: string | undefined) {
        if (name == undefined) {
            return;
        }

        const result = this.checkShadowInParent(sourceFile, aNode, aNode, name);
        if (result == null) {
            return;
        }

        const shadowStartPosition = this.getNodeNamePosition(sourceFile, result);
        const currentStartPosition = this.getNodeNamePosition(sourceFile, aNode);

        // 避免重复名称在当前名称后面
        if (shadowStartPosition.line > currentStartPosition.line) {
            return;
        }

        // 避免重复添加
        const message = "'" + name + "' is already declared in the upper scope on line " + shadowStartPosition.line + ' column ' + shadowStartPosition.character + '.';
        const startLine = currentStartPosition.line;
        const startCol = currentStartPosition.character;
        let include = false;
        for (const def of this.issues) {
            if ((def.defect.reportLine == startLine && def.defect.reportColumn == startCol)) {
                include = true;
                break;
            }
        }
        if (!include) {
            this.addIssueReport(targetFile, startLine, startCol, 0, message);
        }
    }

    private checkShadowInParent(sourceFile: ts.SourceFileLike, sourceNode: ts.Node, currentNode: ts.Node, name: string): ts.Node | undefined {
        const parentNode = currentNode.parent;
        if (parentNode == undefined) {
            return;
        }

        let parents: ts.Node[] = []; // 父节点和父节点的兄弟节点
        const parentParentNode = parentNode.parent;
        if (parentParentNode == undefined) {
            parents.push(parentNode);
        } else {
            parents = parents.concat(parentParentNode.getChildren());
        }

        // 源节点
        const sourceStartPosition = this.getNodeNamePosition(sourceFile, sourceNode);
        const currentStartPosition = this.getNodeNamePosition(sourceFile, currentNode);
        if (currentStartPosition.line > sourceStartPosition.line) {
            return;
        }

        for (const aParent of parents) {
            // 避免重复名称在当前名称后面
            const currentStartPosition = this.getNodeNamePosition(sourceFile, aParent);
            if (currentStartPosition.line > sourceStartPosition.line) {
                continue;
            }

            let foundShadow: ts.Node | undefined;
            const children = aParent.getChildren();
            for (const child of children) {
                const currentStartPosition = this.getNodeNamePosition(sourceFile, child);
                if (currentStartPosition.line > sourceStartPosition.line) {
                    continue;
                }

                const members = this.objectNeedCheck(child, true);
                for (const member of members) {
                    if (member.name == name) {
                        const sourceStartPosition = this.getNodeNamePosition(sourceFile, sourceNode);
                        const currentStartPosition = this.getNodeNamePosition(sourceFile, member.node);
                        if (sourceStartPosition.line != currentStartPosition.line || sourceStartPosition.character != currentStartPosition.character) {
                            foundShadow = member.node;
                            break;
                        }
                    }
                }
            }

            if (foundShadow) {
                return foundShadow;
            }

            let result = this.checkShadowInParent(sourceFile, sourceNode, aParent, name);
            if (result) {
                return result;
            }
        }
    }

    private getNodeNamePosition(sourceFile: ts.SourceFileLike, aNode: ts.Node): ts.LineAndCharacter {
        let node = aNode;
        if (ts.isVariableStatement(aNode)) {
            let result = this.getSpecifyChild(aNode, ts.SyntaxKind.VariableDeclaration);
            if (result) {
                node = result;
            }
        } else if (ts.isNamespaceExport(node) ||
            ts.isInterfaceDeclaration(node) ||
            ts.isClassDeclaration(node) ||
            ts.isFunctionExpression(node) ||
            ts.isFunctionDeclaration(node) ||
            ts.isArrowFunction(node) ||
            ts.isModuleDeclaration(node) ||
            ts.isTypeAliasDeclaration(node))
        {
            let result = this.getSpecifyChild(aNode, ts.SyntaxKind.Identifier);
            if (result) {
                node = result;
            }
        }

        const currentStartPosition = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart());
        const startLine = currentStartPosition.line + 1;
        const startCol = currentStartPosition.character + 1;

        return {line: startLine, character: startCol};
    }

    private getSpecifyChild(aNode: ts.Node, kind: ts.SyntaxKind): ts.Node | undefined {
        for (const child of aNode.getChildren()) {
            if (child.kind == kind) {
                return child;
            }
            let result = this.getSpecifyChild(child, kind);
            if (result) {
                return result;
            }
        }
    }

    /**
     * 节点是否需要被检查
     *
     * @param node 被检查的节点
     * @param forShadow true：检查节点时调用； false: 检查重名时调用
     * @returns 检查结果
     */
    private objectNeedCheck(node: ts.Node, forShadow: boolean): CheckMember[] {
        let members: CheckMember[] = [];
        if (ts.isNamespaceExport(node) ||
            ts.isClassDeclaration(node) ||
            ts.isArrowFunction(node) ||
            ts.isModuleDeclaration(node)) {
            const object = { name: node.name?.text, node: node };
            members.push(object);
        } else if (ts.isInterfaceDeclaration(node)) {
            if (!this.ruleOptions.ignoreTypeValueShadow) {
                const object = { name: node.name?.text, node: node };
                members.push(object);
            }

            if (!forShadow) {
                let lastNode: ts.Node | undefined = undefined;
                for (const child of node.getChildren()) {
                    // interface Test { p1(arg: string): typeof arg; } 中的 arg
                    // 或 interface Test { (arg: string): typeof arg; } 中的 arg
                    if (lastNode && lastNode.kind == ts.SyntaxKind.OpenBraceToken) {
                        if (child.kind != ts.SyntaxKind.SyntaxList) {
                            continue;
                        }

                        if (child.getChildren().length == 0) {
                            continue;
                        }
                        const callObject = child.getChildren()[0];
                        let syntaxObject: ts.Node | undefined = undefined; // arg: string
                        if (ts.isMethodSignature(callObject)) { // p1(arg: string): typeof arg;
                            if (callObject.getChildren().length >= 3) {
                                syntaxObject = callObject.getChildren()[2];
                            }
                        } else  if (ts.isCallSignatureDeclaration(callObject)) {
                            if (callObject.getChildren().length >= 2) {
                                syntaxObject = callObject.getChildren()[1];
                            }
                        }

                        if (syntaxObject == undefined || syntaxObject.kind != ts.SyntaxKind.SyntaxList) {
                            continue;
                        }

                        for (const obj of syntaxObject.getChildren()) {
                            if (!ts.isParameter(obj)) {
                                continue;
                            }
                            const name = obj.name;
                            if (!ts.isIdentifier(name)) {
                                continue;
                            }
                            const objectNode = { name: name.text, node: obj };
                            members.push(objectNode);
                        }
                    }
                    lastNode = child;
                }
            }
        } else if (ts.isTypeAliasDeclaration(node)) {
            if (!this.ruleOptions.ignoreTypeValueShadow) {
                const object = { name: node.name?.text, node: node };
                members.push(object);
            }
        } else if (ts.isVariableStatement(node)) {
            let declarationList = node.declarationList.declarations;
            if (declarationList.length > 0) {
                const firstNode = declarationList[0];
                let varName = firstNode.name;
                if (ts.isIdentifier(varName)) {
                    const object = { name: varName.text, node: node };
                    members.push(object);
                }
            }
        } else if (ts.isFunctionTypeNode(node) && !this.ruleOptions.ignoreFunctionTypeParameterNameValueShadow) { // type Func = (test: string, test2: number) => typeof test;
            // 找出括号内的参数
            let nodeList = node.getChildren();
            if (nodeList.length > 2) {
                const params = nodeList[1].getChildren();
                for (const par of params) {
                    if (!ts.isParameter(par)) {
                        continue;
                    }

                    const parNode = par.name;
                    if (!ts.isIdentifier(parNode)) {
                        continue;
                    }

                    const name = parNode.text;
                    const object = { name: name, node: parNode };
                    members.push(object);
                }
            }
        } else if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
            if (node.name?.text) {
                const object = { name: node.name?.text, node: node };
                members.push(object);
            }

            if (!forShadow) {
                // 类似于右边代码尖括号内的参数：function foo<T>(arg: T) { }
                let lastNode: ts.Node | undefined = undefined;
                for (const child of node.getChildren()) {
                    if (lastNode && lastNode.kind == ts.SyntaxKind.LessThanToken) {
                        if (child.kind != ts.SyntaxKind.SyntaxList) {
                            continue;
                        }

                        for (const obj of child.getChildren()) {
                            if (!ts.isTypeParameterDeclaration(obj)) {
                                continue;
                            }
                            const objectNode = { name: obj.name.text, node: obj };
                            members.push(objectNode);
                        }
                    }
                    lastNode = child;
                }
            }

            if (ts.isFunctionDeclaration(node)) {
                // function foo<T extends (...args: any[]) => any>(fn: T, args: any[]) { } 中第一个args
                let lastNode: ts.Node | undefined = undefined;
                for (const child of node.getChildren()) {
                    if (lastNode && lastNode.kind == ts.SyntaxKind.LessThanToken) {
                        if (child.kind != ts.SyntaxKind.SyntaxList) {
                            continue;
                        }

                        for (const obj of child.getChildren()) {
                            if (!ts.isTypeParameterDeclaration(obj)) {
                                continue;
                            }
                            const name = obj.name;
                            if (!ts.isIdentifier(name)) {
                                continue;
                            }
                            const objectNode = { name: name.text, node: obj };
                            members.push(objectNode);
                        }
                    }
                    lastNode = child;
                }
            } else { // function doThing(foo: number) { } 中的 foo
                let lastNode: ts.Node | undefined = undefined;
                for (const child of node.getChildren()) {
                    if (lastNode && lastNode.kind == ts.SyntaxKind.OpenParenToken) {
                        if (child.kind != ts.SyntaxKind.SyntaxList) {
                            continue;
                        }

                        for (const obj of child.getChildren()) {
                            if (!ts.isParameter(obj)) {
                                continue;
                            }
                            const name = obj.name;
                            if (!ts.isIdentifier(name)) {
                                continue;
                            }
                            const objectNode = { name: name.text, node: obj };
                            members.push(objectNode);
                        }
                    }
                    lastNode = child;
                }
            }
        } else if (ts.isImportDeclaration(node) && forShadow && !this.ruleOptions.ignoreTypeValueShadow) { // import type { foo } from './foo'; 中的 foo
            for (const child of node.getChildren()) {
                if (!ts.isImportClause(child)) {
                    continue;
                }

                for (const element of child.getChildren()) {
                    if (!ts.isNamedImports(element)) {
                        continue;
                    }

                    for (const namedImport of element.elements) {
                        const name = namedImport.name;
                        if (name == undefined || !ts.isIdentifier(name)) {
                            continue;
                        }
                        const objectNode = { name: name.text, node: child };
                        members.push(objectNode);
                    }
                }
            }
        } else if (ts.isVariableDeclaration(node)) {
            const params = this.getParamsInVarFunction(node);
            for (const param of params) {
                const name = param.name;
                if (!ts.isIdentifier(name)) {
                    continue;
                }
                const objectNode = { name: name.text, node: param };
                members.push(objectNode);
            }
        } else if (ts.isParameter(node)) {
            const name = node.name;
            if (ts.isIdentifier(name)) {
                // ...args: any[] 忽略这种，避免重复
                let ignoreNode = false;
                if (node.getChildren().length > 0) {
                    const firstNode = node.getChildren()[0];
                    ignoreNode = ts.isDotDotDotToken(firstNode);
                }
                if (!ignoreNode) {
                    const objectNode = { name: name.text, node: node };
                    members.push(objectNode);
                }
            }
        }
        return members;
    }

    // 找到let x = foo((x, y) => { }); 中的参数x和y
    private getParamsInVarFunction(aNode: ts.Node): ts.ParameterDeclaration[] {
        let paramList: ts.ParameterDeclaration[] = [];

        const variable = this.getSpecifyChild(aNode, ts.SyntaxKind.CallExpression);
        if (variable == undefined) {
            return paramList;
        }

        const syntaxList = this.getSpecifyChild(variable, ts.SyntaxKind.SyntaxList);
        if (syntaxList == undefined) {
            return paramList;
        }

        const func = this.getSpecifyChild(syntaxList, ts.SyntaxKind.ArrowFunction);
        if (func == undefined) {
            return paramList;
        }

        const paramSyntaxList = this.getSpecifyChild(func, ts.SyntaxKind.SyntaxList);
        if (paramSyntaxList == undefined) {
            return paramList;
        }

        for (const param of paramSyntaxList.getChildren()) {
            if (ts.isParameter(param)) {
                paramList.push(param);
            }
        }

        return paramList;
    }

    private addIssueReport(arkFile: ArkFile, line: number, startCol: number, endCol: number, message: string) {
        const severity = this.rule.alert ?? this.metaData.severity;
        const filePath = arkFile.getFilePath();
        const fixKey = line + ':' + startCol;
        const mergeKey = filePath + '%' + fixKey + '%' + message.padEnd(130) + this.rule.ruleId;
        const defect = new Defects(line, startCol, message, severity, this.rule.ruleId,
            mergeKey, this.metaData.ruleDocPath, true, false, false, fixKey);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }
}